package cn.caplike.demo.spring.security.dynamic.authorization.configuration.security;

import cn.caplike.data.redis.service.spring.boot.starter.RedisService;
import cn.caplike.demo.spring.security.dynamic.authorization.configuration.security.filter.HttpServletRequestWrapFilter;
import cn.caplike.demo.spring.security.dynamic.authorization.configuration.security.filter.JWTAuthenticationFilter;
import cn.caplike.demo.spring.security.dynamic.authorization.configuration.security.filter.JWTAuthorizationFilter;
import cn.caplike.demo.spring.security.dynamic.authorization.configuration.security.popedom.FilterSecurityInterceptorPostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;

/**
 * Security 配置类
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-05-06 10:06
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    // ~ Constant
    // -----------------------------------------------------------------------------------------------------------------

    public static final String ACCESS_TOKEN = "access-token";

    public static final String CSRF_TOKEN = "csrf-token";

    public static final String LOGIN_URI = "/auth/login";

    public static final String REGISTER_URI = "/auth/register";

    // ~ Authentication configure
    // -----------------------------------------------------------------------------------------------------------------

    /**
     * AuthenticationProvider
     */
    private AuthenticationProvider authenticationProvider;
    /**
     * AccessDecisionManager
     */
    private AccessDecisionManager accessDecisionManager;

    // ~ HttpSecurity configure
    // -----------------------------------------------------------------------------------------------------------------

    /**
     * SecurityMetadataSource
     */
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    /**
     * {@link CsrfTokenRedisRepository}
     */
    private CsrfTokenRepository csrfTokenRepository;

    /**
     * {@link RedisService}
     */
    private RedisService redisService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // ~ CSRF
                // -----------------------------------------------------------------------------------------------------
                .csrf().ignoringAntMatchers(LOGIN_URI, REGISTER_URI).csrfTokenRepository(csrfTokenRepository)
                .and()
                .addFilterBefore(new HttpServletRequestWrapFilter(), CsrfFilter.class)

                .authorizeRequests()

                // ~ 基础权限设定
                // -----------------------------------------------------------------------------------------------------
                .antMatchers("/auth/**").permitAll()

                // ~ 动态权限设定
                // -----------------------------------------------------------------------------------------------------
                .anyRequest().authenticated()
                .withObjectPostProcessor(new FilterSecurityInterceptorPostProcessor(accessDecisionManager, securityMetadataSource))

                // ~ 禁用 Session: Spring Security will never create an HttpSession and it will never use it to obtain the SecurityContext
                // -----------------------------------------------------------------------------------------------------
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // ~ 添加 JWTAuthenticationFilter 和 JWTAuthorizationFilter
                // -----------------------------------------------------------------------------------------------------
                .and()
                .addFilterAt(new JWTAuthenticationFilter(authenticationManager(), redisService), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(new JWTAuthorizationFilter(redisService), JWTAuthenticationFilter.class)

                // ~ 异常处理: 处理 AccessDeniedException 和 AuthenticationException
                // -----------------------------------------------------------------------------------------------------
                .exceptionHandling()
                .accessDeniedHandler(new CustomAccessDeniedHandler())
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint());
    }

    // ~ Autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setAccessDecisionManager(@Qualifier("dynamicAccessDecisionManager") AccessDecisionManager accessDecisionManager) {
        this.accessDecisionManager = accessDecisionManager;
    }

    @Autowired
    public void setSecurityMetadataSource(@Qualifier("dynamicFilterInvocationSecurityMetadataSource") FilterInvocationSecurityMetadataSource securityMetadataSource) {
        this.securityMetadataSource = securityMetadataSource;
    }

    @Autowired
    public void setAuthenticationProvider(@Qualifier("customAuthenticationProvider") AuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    @Autowired
    public void setCsrfTokenRepository(@Qualifier("csrfTokenRedisRepository") CsrfTokenRepository csrfTokenRepository) {
        this.csrfTokenRepository = csrfTokenRepository;
    }
}
